 
/*
 * @Author: ZhouNan
 * @Date: 2022-04-16 21:30:56
 * @LastEditors: ZhouNan
 * @LastEditTime: 2022-04-22 00:23:56
 * @FilePath: /export_ts/src/core/02_Export.ts
 * @Description: 导出脚本
 */

import Cache from './00_Cache';
import { NodeTreeStruct } from "./01_ExtractAssetInfo";
import { EXPORT_CONFIG_PATH, TEMP_PATH } from "../const";
import { readJsonSync } from "fs-extra";
import { AssetInfo } from "../../@types/packages/asset-db/@types/public";
import { existsSync, readFileSync } from "fs";
import util from "util";

let replacePropReg = new RegExp(`\/\/ Auto export attr, Don't change[\\w\\W]*// Auto export attr end`);
let replacePropFlag = `// Auto export attr, Don't change\n%s\t// Auto export attr end`;

let replaceBindReg = new RegExp(`\/\/ Auto export bind, Don't change[\\w\\W]*// Auto export bind end`);
let replaceBindFlag = `// Auto export bind, Don't change \n%s\t\t// Auto export bind end`;

let export_flag = '';
let comp_short_conf: {} = null!;
let autoBind = false;
let addProp = false;
async function start(nodeTree: NodeTreeStruct, assetInfo: AssetInfo) {
    console.log(`[Export-TS] auto export script start >>> ${assetInfo.url}`);
    let config = readJsonSync(EXPORT_CONFIG_PATH);
    export_flag = config.export_flag;
    comp_short_conf = convertCompFlags(config.comp_flags); // 组件缩写表
    autoBind = config.bind_comp; // 自动
    addProp = config.export_property; // 添加 @property()

    checkTheSameName(nodeTree);
    let exportInfo = convertToExportInfo(nodeTree);
    let propertStr = genPropertyStr(exportInfo); 
    let autoBindStr = autoBind? genAutoBindStr(exportInfo, assetInfo.type): '';
    let filePath = getExportFilePath(assetInfo);
    let scriptInfo = await exportFile(filePath, propertStr, autoBindStr);
    if (scriptInfo) {
        Cache.addCache(scriptInfo, assetInfo);
    }
}

/** 检测导出的同名节点 只做警告 任然导出 */
function checkTheSameName(nodeTree: NodeTreeStruct) {
    let map:{[key:string]: any[]} = {};
    deepCheckTheSameName(nodeTree, map);
    for (let k in map) {
        let arr = map[k];
        if (arr.length > 1) {
            console.warn('[Export-TS] Exist the same name node, please rename those node');
            arr.forEach(node => {
                console.warn(`>>> ${getNodeFullPathInNodeTree(node)}`)
            })
            console.warn('-----------------------------------------------');
        }
    }
}
function deepCheckTheSameName(node: NodeTreeStruct, map: any) {
    if (!map[node.name]) {
        map[node.name] = [];
    }
    if (node.isExport) {
        map[node.name].push(node);
    }
    // 对子节点继续
    node.children.forEach(child => {
        deepCheckTheSameName(child, map);
    })
}

/** 转换成导出需要的信息 */
function convertToExportInfo(nodeTree: NodeTreeStruct): ExportInfoStruct[] {
    let data = {
        count: 0, // 暂时没有用到
        exportArr: []
    }
    deepConvert(nodeTree, data);
    return data.exportArr
}

function deepConvert(node: NodeTreeStruct, data: any, parent?: ExportInfoStruct) {
    let exportInfo = {
        isLocal: false, // 是否是局部变量
        isProperty: false, // 是否是成员变量
        path: '', // 节点路径 root.node.node
        name: node.name, // 导出节点名字
        type: '', // 导出的类型
    }
    // 导出类型
    exportInfo.type = getExportType(node);
    if (node.isExport) {
        exportInfo.isProperty = true;
    }
    // 子节点多于一个导出时 先变成局部变量
    if (node.childExportCount > 1 || (node.childExportCount > 0 && exportInfo.type !== 'cc.Node')) {
        exportInfo.isLocal = true;
    }
    // 寻找节点路径
    if (!node.parent) {
        exportInfo.path = 'root';
    }
    else if (parent?.isLocal) {
        exportInfo.path = `${convertNodeNameToPropName(node.parent.name)}.${node.name}`;
    }
    else if (parent?.isProperty) {
        exportInfo.path = `this.${convertNodeNameToPropName(node.parent.name)}.${node.name}`;
    }
    else {
        exportInfo.path = `${parent?.path}.${node.name}`;
    }
    data.exportArr.push(exportInfo);
    // 对子节点继续
    node.children.forEach(child => {
        deepConvert(child, data, exportInfo);
    })
}

/** 生成成员变量 */
function genPropertyStr(exportInfo: ExportInfoStruct[]) {
    let str = '';
    exportInfo.forEach(info => {
        if (info.isProperty) {
            if (addProp) {
                str += `\t@property(${info.type})\n`;
            }
            str += `\tprivate ${convertNodeNameToPropName(info.name)}: ${info.type} = null;\n`;
        }
    })
    return str;
}

/** 生成自动绑定 */
function genAutoBindStr(exportInfo: ExportInfoStruct[], assetType: string): string {
    let str = '';
    for (let i = 0; i < exportInfo.length; i++) {
        let info = exportInfo[i];
        let propName = convertNodeNameToPropName(info.name)
        if (info.isLocal){ // 先转换成局部变量
            if (info.path == 'root') {
                if (assetType == 'cc.SceneAsset') {
                    str += `\t\tlet ${propName} = this.node.parent;\n`;
                }
                else {
                    str += `\t\tlet ${propName} = this.node;\n`;
                }
                continue ;
            }
            else {
                str += `\t\tlet ${propName} = ${genAutoBindByPath(info.path, assetType)};\n`;
            }
        }
        if (info.isProperty) { // 是成员变量的直接用成员变量
            // 先获取节点
            if (info.isLocal) {
                str += `\t\tthis.${propName} = ${propName}`
            }
            else {
                str += `\t\tthis.${propName} = ${genAutoBindByPath(info.path, assetType)}`;
            }
            // 是组件就获取组件
            if (info.type == 'cc.Node') {
                str += `;\n`;
            }
            else {
                str += `.getComponent(${info.type});\n`;
            }
        }
    }
    return str
}

function genAutoBindByPath(path: string, assetType: string) {
    let arr = path.split('.');
    let str, start;
    if (arr[0] == 'this') {
        str = `${arr[0]}.${arr[1]}`;
        start = 2;
    }
    else if (arr[0] == 'root') {
        if (assetType == 'cc.SceneAsset') {
            str = `this.node.parent`;
        }
        else {
            str = `this.node`;
        }
        start = 1;
    }
    else {
        str = arr[0];
        start = 1
    }
    for (let i = start; i < arr.length; i++) {
        str += `.getChildByName('${arr[i]}')`;
    }
    return str;
}

/** 获取导出的类型 */
function getExportType(node: NodeTreeStruct): string {
    let name = node.name;
    let flagLength = export_flag.length;
    let exceptFlag = name.substring(flagLength, name.length - 1);
    for (let k in comp_short_conf) {
        if (exceptFlag.startsWith(k)) {
            // @ts-ignore
            let compKey = comp_short_conf[k];
            if (autoBind && node.components.indexOf(compKey) == -1) {
                console.warn(`[Export-TS] node export component ${compKey}, but not found !  >>>  ${getNodeFullPathInNodeTree(node)}`);
            }
            return compKey;
        }
    }
    return 'cc.Node';
}

/** 获得文件的导出路径 */
function getExportFilePath(assetInfo: AssetInfo) {
    let exportPath = '';
    let cache = Cache.getAssetCache(assetInfo.uuid);
    if (cache) {
        exportPath = cache.url;
    }
    else {
        let path = assetInfo.path;
        exportPath = path + '.ts';
    }
    return exportPath;
}

async function exportFile(exportPath: string, propStr: string, bindStr: string) {
    let fullPath = await Editor.Message.request('asset-db', 'query-path', exportPath);
    let scriptInfo: AssetInfo | null;
    if (fullPath && existsSync(fullPath)) { // 如果文件已存在，就替换自动导出部分的内容
        //     let scriptPath = Editor.url(filePath);
        let scriptTemplate = readFileSync(fullPath, {encoding: 'utf-8'}) + '';
        let str1 = util.format(replacePropFlag, propStr)
        scriptTemplate = scriptTemplate.replace(replacePropReg, str1);
        let str2 = util.format(replaceBindFlag, bindStr)
        scriptTemplate = scriptTemplate.replace(replaceBindReg, str2);
        scriptInfo = await Editor.Message.request('asset-db', 'save-asset', exportPath, scriptTemplate);
    }
    else { // 不存在就按模版文件创建
        let clazzName = Editor.Utils.Path.basenameNoExt(exportPath);
        clazzName = convertNodeNameToPropName(clazzName);
        let scriptTemplate = readFileSync(TEMP_PATH, {encoding: "utf-8"});
        scriptTemplate = scriptTemplate.replace(/_CLASS_NAME_/g, clazzName)
        let date = new Date(Date.now())
        scriptTemplate = scriptTemplate.replace(/_DATE_/g, `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`);
        // 替换成员变量
        let str1 = util.format(replacePropFlag, propStr)
        scriptTemplate = scriptTemplate.replace(replacePropReg, str1);
        // 替换bind
        let str2 = util.format(replaceBindFlag, bindStr)
        scriptTemplate = scriptTemplate.replace(replaceBindReg, str2);
        scriptInfo = await Editor.Message.request('asset-db', 'create-asset', exportPath, scriptTemplate);
    }
    console.log(`[Export-TS] export script path >>> ${fullPath}`);
    return scriptInfo;
}

function convertCompFlags(compFlags: any[]) {
    let data = {}
    compFlags.forEach(info => {
        // @ts-ignore
        data[info.value] = info.key;
    })
    return data
}

/** 获取节点在节点树上的全路径 */
function getNodeFullPathInNodeTree(node: NodeTreeStruct, preStr: string = ''): string {
    if (preStr == '') {
        preStr = `${node.name}`
    }
    else {
        preStr = `${node.name}.${preStr}`;
    }
    if (node.parent) {
        return getNodeFullPathInNodeTree(node.parent, preStr);
    }
    return preStr;
}

/** 除了导出标记为以为的所有标点符号转化为 '_' */
function convertNodeNameToPropName(nodeName: string): string {
    let isHaveFlag = nodeName.startsWith(export_flag)
    if (isHaveFlag) {
        let flagLength = export_flag.length;
        nodeName = nodeName.substring(flagLength, nodeName.length);
    }
    let propName = nodeName.trim();
    propName = propName.replace(/\W/g, '_');
    if (isHaveFlag) {
        propName = export_flag + propName;
    }
    return propName;
}

export default {
    start
}

type ExportInfoStruct = {
    isLocal: boolean, // 是否是局部变量
    isProperty: boolean, // 是否是成员变量
    idx?: number, // 用于防止局部变量命名重复
    path: string, // 节点路径 root.node.node
    name: string, // 导出节点名字
    type: string, // 导出的类型
}
